/*
    This file is part of FSJController.

    FSJController is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    FSJController is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with FSJController.  If not, see <http://www.gnu.org/licenses/>.
*/

package de.qsc.fsjcontroller;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import de.qsc.fsjcontroller.FSConferenceMember.State;

/**
 * Encapsulates a Freeswitch telephone conference (mod_conference)
 * 
 * @author Roland Hänel <rha@qsc.de>
 */
public class FSConference implements FSConferenceHandler {
	
    //private static final Logger logger = LoggerFactory.getLogger(FSConference.class);

    FSController controller;
    protected String name;
    protected Map<UUID, FSConferenceMember> members;
    FSConferenceMember floor;

    /**
     * Creates a conference object and registers to the given FreeSwitch controller
     * 
     * @param controller
     *            the FreeSwitch controller to register this conference to
     * @param name
     *            the name of the conference (used by FreeSwitch to identify the conference)
     */
    public FSConference(FSController controller, String name) {
        this.controller = controller;
        this.name = name;
        members = Collections.synchronizedMap(new HashMap<UUID, FSConferenceMember>());
        floor = null;
        controller.registerConference(name, this);
    }
	
    /**
     * @return the name of this conference
     */
    public String getName() {
        return name;
    }

    /**
     * @return the conference member that has the floor, or null
     */
    public FSConferenceMember getFloor() {
        return floor;
    }

    /**
     * Unregister from the associated FreeSwitch controller, thus deleting the reference to this
     * object there and stop listening to events.
     */
    public void unregister() {
        controller.unregisterConference(name);
    }

    
    public void addMember (FSChannel channel) {
        members.put(channel.getUUID(), new FSConferenceMember(channel));
    }
    
    /**
     * Asks the FreeSwitch controller to dial a phone number and try to create a new conference
     * member on the resulting connection
     * 
     * @param phone
     *            the phone number to dial
     * @return the UUID the new member will be assigned when she joins the conference, or null if
     *         the command could not be queued to the FreeSwitch controller
     */
    public void dialMember(String gateway, String from, String to) {
        
        // create FreeSwitch command to dial the new member, override
        // stateTrigger() callback to add the newly created participant
        // as a conference member
        // TODO
        controller.originateChannel(gateway, new FSChannel(UUID.randomUUID(),controller, from, to, false) {

            // TODO
            public void onFailure() {                
            }
            
            public void onEvent(FSEvent event) {
                
                if (event.getName().equals(FSEvent.Name.CHANNEL_PARK)) {
                    FSConferenceMember member;
                    member = members.get(getUUID());
                    if (member == null) {
                        addMember(this);
                    }     

                    stateTrigger(member);
                    runApplication(
                            "play_and_get_digits",
                            "1 1 1 10000 * ${qloud_conf_invite} _undef_ dtmf #"
                            );

                }
                    
                if (event.getName().equals(FSEvent.Name.CHANNEL_EXECUTE_COMPLETE)) {
                    String dtmf = event.getAttribute("variable_dtmf");
                    if (dtmf != null && dtmf.equals("#")) {
                        logger.debug("dtmf was correct");
                        runApplication("conference", name);
                    } else {
                        logger.debug("dtmf was wrong");
                        controller.queueCommand(new FSAPICommand("uuid_kill", getUUID().toString(), null));
                        FSConferenceMember member = members.remove(getUUID());
                        if (member != null)
                            member.setState(FSConferenceMember.State.FAILED);
                        stateTrigger(member);
                    }
                }
            }
        });
        // return callUUID;
    }

    /**
     * Asks the FreeSwitch controller to kick a specific conference member
     * 
     * @param memberUUID
     *            the uuid of the conference member
     * @return true if the command was successfully queued on the controller
     */
    public boolean kickMember(final UUID memberUUID) {
        FSCommand command;
        FSConferenceMember member = members.get(memberUUID);
        if (member == null || member.state == FSConferenceMember.State.CALLING)
            command = new FSAPICommand("uuid_kill", memberUUID.toString(), null);
        else {
            controller.addEventHandler(new FSEventCallback(new FSEventNameUUIDMatcher(FSEvent.Name.CHANNEL_EXECUTE_COMPLETE, memberUUID)) {

                public void handleEvent(FSEvent event) {
                    controller.removeEventHandler(this);
                    controller.queueCommand(new FSAPICommand("uuid_kill", memberUUID.toString(), null));
                }
            });
            command = new FSAPICommand("conference", name + " kick " + member.getId(), new FSAPICommandCallback() {

                @Override
                public void onSuccess(FSEvent event) {
                    //TODO keine variabeln
                    controller.queueCommand(new FSApplicationCommand(memberUUID, "playback", "${qloud_conf_kick}"));
                }

                @Override
                public void onError(FSEvent event) {
                    //TODO keine variabeln
                    controller.queueCommand(new FSApplicationCommand(memberUUID, "playback", "${qloud_conf_kick}"));
                }
                
                @Override
                public void onFailure(String reply) {
                    // should not happen
                }
            });

        }
        return controller.queueCommand(command);
    }

    /**
     * Asks the FreeSwitch controller to kick all members from this conference
     * 
     * @return true if the command was successfully queued on the controller
     */
    public boolean kickAllMembers() {
        boolean retval = true;
        for (UUID uuid : members.keySet())
            if (kickMember(uuid) == false)
                retval = false;
        return retval;
    }

    /**
     * Asks the FreeSwitch controller to mute/unmute a specific conference member
     * 
     * @param uuid
     *            the uuid of the conference member
     * @param mute
     *            true to mute the member, false to unmute the member
     * @return true if the command was successfully queued on the controller
     */
    public boolean muteMember(UUID uuid, boolean mute) {
        FSConferenceMember member = members.get(uuid);
        if (member == null)
            return false;
        FSCommand command = new FSAPICommand("conference", name + (mute ? " mute " : " unmute ") + member.getId(), null);
        return controller.queueCommand(command);
    }

    /**
     * Asks the FreeSwitch controller to deaf/undeaf a specific conference member
     * 
     * @param uuid
     *            the uuid of the conference member
     * @param deaf
     *            true to deaf the member, false to undeaf the member
     * @return true if the command was successfully queued on the controller
     */
    public boolean deafMember(UUID uuid, boolean deaf) {
        FSConferenceMember member = members.get(uuid);
        if (member == null)
            return false;
        FSCommand command = new FSAPICommand("conference", name + (deaf ? " deaf " : " undeaf ") + member.getId(), null);
        return controller.queueCommand(command);
    }

    /**
     * This method is called from the associated FreeSwitch controller if it has an event that is
     * associated with this conference.
     * 
     * @param event
     *            the event to be processed by this entity
     */
    public void processEvent(FSEvent event) {
        // extract action from the event
        String action = event.getAttribute("Action");
        if (action == null)
            return;

        int memberId;
        String uuidString;
        UUID uuid;
        FSConferenceMember member;
        try {
            memberId = Integer.parseInt(event.getAttribute("Member-Id"));
            uuidString = event.getAttribute("Unique-ID");
            uuid = UUID.fromString(uuidString);
            member = members.get(uuid);
        } catch (Exception e) {
            return;
        }

        if (action.equals("add-member")) {
            if (member == null) {
                member = new FSConferenceMember(controller.getChannel(uuid));
                members.put(uuid, member);
            }
            member.setId(memberId);
            member.setState(State.ACTIVE);
        } else if (action.equals("del-member")) {
            members.remove(uuid);
            if (member != null)
                member.setState(State.GONE);
        } else if (action.equals("floor-change")) {
            floor = member;
        } else if (action.equals("start-talking") && member != null) {
            member.setTalking(true);
        } else if (action.equals("stop-talking") && member != null) {
            member.setTalking(false);
        } else if (action.equals("kick-member") && member != null) {
            if (member.getState() != FSConferenceMember.State.GONE && member.getState() != FSConferenceMember.State.FAILED)
                member.setState(State.KICKED);
        } else if (action.equals("deaf-member") && member != null) {
            member.setDeaf(true);
        } else if (action.equals("undeaf-member") && member != null) {
            member.setDeaf(false);
        } else if (action.equals("mute-member") && member != null) {
            member.setMute(true);
        } else if (action.equals("unmute-member") && member != null) {
            member.setMute(false);
        } else {
            // some event that we don't handle (yet)
            return;
        }
        // call state trigger method to make the application aware of the changes
        stateTrigger(member);
    }
	
    /**
     * Gets a specific member of this conference.
     * 
     * @param uuid
     *            the uuid of the member to return
     * @return the requested member if it exists, null otherwise
     */
    public FSConferenceMember getMember(UUID uuid) {
        return members.get(uuid);
    }

    /**
     * Gets the members of this conference. When iterating over this map, clients need to make sure
     * to synchronize on it, because the FreeSwitch controller might need to make changes to the map
     * concurrently.
     * 
     * <pre>
     * m = myconf.getMembers();
     * synchronized (m) {
     *    for (FSMember member : m) {
     *       ...
     *    }
     * }
     * </pre>
     * 
     * @return a map of the conference member objects
     */
    public Map<UUID, FSConferenceMember> getMembers() {
        return members;
    }

    /**
     * This function is triggered by the FreeSwitch controller if the state of the conference (i.e.,
     * the state of its members) has changed. It can be overridden by custom classes to implement
     * functionality that needs to be performed on state changes. This function should not block,
     * otherwise the FreeSwitch controller might become unresponsive.
     */
    public void stateTrigger(FSConferenceMember member) {
    }

}
